package figure8.Network;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import figure8.Network.Message.Type;
import figure8.events.NetworkDisconnectEvent;
import figure8.events.NetworkEventListener;
import figure8.events.NetworkEventSource;
import figure8.events.NetworkMessageEvent;
import figure8.events.NetworkNewClientEvent;

public class Server implements Runnable, NetworkEventListener, NetworkConnection {
	protected ServerSocket server = null;
	
	protected Map<Integer, Client> clients;
	protected int nextID = 1;
	
	protected List<NetworkEventListener> listeners;
	
	protected boolean disconnect = false;
	
	public static void main(String[] args) {
		Server server = new Server(8888);
		
		(new Thread(server)).start();
	}
	
	public Server(int port) {
		this.clients = new HashMap<Integer, Client>();
		this.listeners = new LinkedList<NetworkEventListener>();
		
		try {
			server = new ServerSocket(port);
		} catch (IOException e) {
			System.err.println("Cannot host, could not listen on port " + port);
		}
	}
	
	@Override
	public void run() {
		try {
		    while (true) {
		    	Socket socket = server.accept();
		    
		    	System.out.println("Someone connected from " + socket.getInetAddress());
		    
		    	Client client = this.newClient(socket);
		    
		    	(new Thread(client)).start();
		    	
		    	//client.send(new Message(Type.CLIENT_CHAT, "test"));
		    }
		} catch (IOException e) {
		    System.out.println("Accept failed");
		}
	}
	
	private Client newClient(Socket socket) {
		int id = this.nextID;
		++this.nextID;
		
		Client client = new Client(socket, true, id);
		
		this.clients.put(id, client);
		
		this.networkNewClient(new NetworkNewClientEvent(this, client));
		
		client.addNetworkEventListener(this);
		
		return client;
	}
	
	public void send(Message message) {
		for (Client client : this.clients.values()) {
			client.send(message);
		}
	}
	
	public void send(Message message, Client exclude) {
		for (Client client : this.clients.values()) {
			if (client != exclude) {
				client.send(message);
			}
		}
	}
	
	private void send(Message message, int id) {
		this.clients.get(id).send(message);
	}

	@Override
	public void networkDisconnect(NetworkDisconnectEvent event) {
	}
	
	@Override
	public void networkMessage(NetworkMessageEvent event) {
		//Forward all network events to all clients
		for (Client client : this.clients.values()) {
			client.send(event.getMessage());
		}
		
		this.fireNetworkMessageEvent(event);
	}
	
	private void fireNetworkMessageEvent(NetworkMessageEvent event) {
		for (NetworkEventListener listener : this.listeners) {
			listener.networkMessage(event);
		}
	}

	@Override
	public void addNetworkEventListener(NetworkEventListener listener) {
		for (Client client : this.clients.values()) {
			client.addNetworkEventListener(listener);
		}
		
		this.listeners.add(listener);
	}
	
	@Override
	public void removeNetworkEventListener(NetworkEventListener listener) {
		for (Client client : this.clients.values()) {
			client.removeNetworkEventListener(listener);
		}
		
		this.listeners.remove(listener);
	}
	
	public void disconnect() {
		this.disconnect = true;
		
		try {
			this.server.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		for (Client client : this.clients.values()) {
			client.disconnect();
		}
	}

	@Override
	public void networkNewClient(NetworkNewClientEvent event) {
		Client client = event.getClient();
		
		client.send(new Message(Type.YOU_ARE, "" + client.id));
		
		this.fireNetworkNewClientEvent(client);
	}
	
	protected void fireNetworkNewClientEvent(Client client) {
		for (NetworkEventListener listener : this.listeners) {
			listener.networkNewClient(new NetworkNewClientEvent(this, client));
		}
	}

	@Override
	public boolean isHost() {
		return true;
	}
}
